#include <CL/cl.hpp>
#include <time.h>
#include "init_opencl.hpp"
#include <fstream>
#include <iostream>
#include "ellpack_opencl_twodev.hpp"
#include "matrix_operations.hpp"

using namespace std;

float * findProductEllPackOCLTwoDev(OpenCLEnv openCLEnv, MatrixOperations matrix, MatrixOperations v, int nrTimes) {
	clock_t init, final;

	int rows = matrix.getNumRows();
	int columns = matrix.getNumCols();
	float * mValues = matrix.getEllPackValues();
	float * vValues = v.getMatrix();
	int * colIdx = matrix.getEllPackColIdx();
	float * answer = new float[rows];
	int maxNonZerosPerRow = matrix.getMaxNonZerosPerRow();

	int valuesLength = maxNonZerosPerRow*rows;

	float * mValues1;
	float * mValues2;
	int * colIdx1;
	int * colIdx2;
	float * answer1;
	float * answer2;

	int firstNrRows = 0;
	int secondNrRows = 0;
	int firstValuesLength = 0;
	int secondValuesLength = 0;

	if(rows%2==1) {
		firstNrRows = rows / 2 + 1;
		secondNrRows = rows / 2;
		firstValuesLength = firstNrRows * maxNonZerosPerRow;
		secondValuesLength = secondNrRows * maxNonZerosPerRow;
	} else {
		firstNrRows = rows / 2;
		secondNrRows = rows / 2;
		firstValuesLength = firstNrRows * maxNonZerosPerRow;
		secondValuesLength = secondNrRows * maxNonZerosPerRow;
	}

	mValues1 = new float[firstValuesLength];
	colIdx1 = new int[firstValuesLength];
	answer1 = new float[firstNrRows];
	mValues2 = new float[secondValuesLength];
	colIdx2 = new int[secondValuesLength];
	answer2 = new float[secondNrRows];

	int j = 0;
	for(int i = 0; i < firstValuesLength; i++) {
		mValues1[i] = mValues[j];
		colIdx1[i] = colIdx[j];
		j++;
	}
	for(int i = 0; i < secondValuesLength; i++) {
		mValues2[i] = mValues[j];
		colIdx2[i] = colIdx[j];
		j++;
	}
//	cout << "j: " << j << endl;
//	cout << "mnz: " << maxNonZerosPerRow << endl;
//	cout << "colidx2 " << colIdx2[0] << endl;

	cl::Context context = openCLEnv.getContext();
	cl::CommandQueue queue = openCLEnv.getQueue();
	cl::CommandQueue queue2 = openCLEnv.getQueue2();

	// Read source file
	ifstream sourceFile("ellpack_opencl_twodev.cl");
	string sourceCode(
		istreambuf_iterator<char>(sourceFile),
		(istreambuf_iterator<char>()));
	cl::Program::Sources source(1, make_pair(sourceCode.c_str(), sourceCode.length()+1));

	// Make program of the source code in the context
	cl::Program program = cl::Program(context, source);

	// Build program for these specific devices
	program.build(openCLEnv.getDevices());

    //In cl::ImageFormat :
		//CL_FLOAT - float values
		//CL_R - 1 value per coord, RGBA - 4 values per coord (pixel)
		cl::Image2D* img = new cl::Image2D(context,CL_MEM_READ_ONLY,cl::ImageFormat(CL_RGBA, CL_FLOAT),1,8192,0);
        cl::Image2D* img2 = new cl::Image2D(context,CL_MEM_READ_ONLY,cl::ImageFormat(CL_RGBA, CL_FLOAT),1,8192,0);
		// The origin is the same as the indices for the first value
		// The z component must be 0 for 2D images
		cl::size_t<3> origin;
		origin.push_back(0);
		origin.push_back(0);
		origin.push_back(0);

		// The region is the same as the dimensions of the image
		// The z component must be 1 for 2D images
		cl::size_t<3> region;
		region.push_back(1);
		region.push_back(columns/4+1);
		region.push_back(1);

		// The row pitch is the number of bytes in each row
		size_t row_pitch = 4 * sizeof(float);

		queue.enqueueWriteImage(*img,
			CL_TRUE,
			origin, region,
			row_pitch, 0,
			(void*) vValues);

        queue2.enqueueWriteImage(*img2,
			CL_TRUE,
			origin, region,
			row_pitch, 0,
			(void*) vValues);

	// Make kernel
	cl::Kernel kernel(program, "ellpack_opencl_twodev");
	cl::Kernel kernel2(program, "ellpack_opencl_twodev");

    //cl::Buffer bufferVector1 = cl::Buffer(context, CL_MEM_READ_ONLY,  columns * sizeof(float));
    //cl::Buffer bufferVector2 = cl::Buffer(context, CL_MEM_READ_ONLY,  columns * sizeof(float));

	cl::Buffer bufferValues1 = cl::Buffer(context, CL_MEM_READ_ONLY, firstValuesLength * sizeof(float));
	cl::Buffer bufferColIdx1 = cl::Buffer(context, CL_MEM_READ_ONLY, firstValuesLength * sizeof(int));
	cl::Buffer bufferAnswer1 = cl::Buffer(context, CL_MEM_WRITE_ONLY, firstNrRows * sizeof(float));

	cl::Buffer bufferValues2 = cl::Buffer(context, CL_MEM_READ_ONLY, secondValuesLength * sizeof(float));
	cl::Buffer bufferColIdx2 = cl::Buffer(context, CL_MEM_READ_ONLY, secondValuesLength * sizeof(int));
	cl::Buffer bufferAnswer2 = cl::Buffer(context, CL_MEM_WRITE_ONLY, secondNrRows * sizeof(float));

	queue.enqueueWriteBuffer(bufferValues1, CL_TRUE, 0, firstValuesLength * sizeof(float), mValues1);
	queue.enqueueWriteBuffer(bufferColIdx1, CL_TRUE, 0, firstValuesLength * sizeof(int), colIdx1);
    //queue.enqueueWriteBuffer(bufferVector1, CL_TRUE, 0, columns * sizeof(float), vValues);

	queue2.enqueueWriteBuffer(bufferValues2, CL_TRUE, 0, secondValuesLength * sizeof(float), mValues2);
	queue2.enqueueWriteBuffer(bufferColIdx2, CL_TRUE, 0, secondValuesLength * sizeof(int), colIdx2);
    //queue2.enqueueWriteBuffer(bufferVector2, CL_TRUE, 0, columns * sizeof(float), vValues);

	kernel.setArg(0, bufferValues1);
	kernel.setArg(1, bufferColIdx1);
	kernel.setArg(2, firstNrRows);
	kernel.setArg(3, maxNonZerosPerRow);
	//kernel.setArg(4, bufferVector1);
	kernel.setArg(4, *img);
	kernel.setArg(5, bufferAnswer1);
	kernel.setArg(6, 0);

	kernel2.setArg(0, bufferValues2);
	kernel2.setArg(1, bufferColIdx2);
	kernel2.setArg(2, secondNrRows);
	kernel2.setArg(3, maxNonZerosPerRow);
	//kernel2.setArg(4, bufferVector2);
	kernel2.setArg(4, *img2);
	kernel2.setArg(5, bufferAnswer2);
	kernel2.setArg(6, firstNrRows);

	cl::NDRange global(firstNrRows);
	cl::NDRange local(1);

	cl::NDRange global2(secondNrRows);
	cl::NDRange local2(1);

	init=clock();
	for (int i = 0; i < nrTimes; i++) {
		queue.enqueueNDRangeKernel(kernel, cl::NullRange, global, local);
		queue2.enqueueNDRangeKernel(kernel2, cl::NullRange, global2, local2);
		queue.enqueueReadBuffer(bufferAnswer1, CL_FALSE, 0, firstNrRows * sizeof(float), answer1);
		queue2.enqueueReadBuffer(bufferAnswer2, CL_FALSE, 0, secondNrRows * sizeof(float), answer2);
//		firstRead.wait();
        queue.finish();
        queue2.finish();
	}
	final=clock()-init;

	double time = (double)final / ((double)CLOCKS_PER_SEC);
	cout << "Calculation took: " << time << " seconds. (EllPack OpenCL, two devices, running kernel)" << endl;
	cout << "ELL opencl, time per: " << time/nrTimes << endl;


	j = 0;
	for(int i = 0; i < firstNrRows; i++) {
		answer[j] = answer1[i];
		j++;
	}
	for(int i = 0; i < secondNrRows; i++) {
		answer[j] = answer2[i];
		j++;
	}
	return answer;
}
